package net.javalib.flickr.search;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import org.apache.http.HttpVersion;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;

import net.javalib.flickr.search.activities.AbstractBaseActivity;
import net.javalib.flickr.search.activities.MainActivity;
import net.javalib.flickr.search.flickr.FlickrImage;
import net.javalib.flickr.search.util.CacheUtil;
import net.javalib.flickr.search.util.ProgressAwareInputStream.OnProgressListener;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
import android.widget.RemoteViews;

/**
 * Application class.
 * 
 * Maintains search result cache etc...
 */
public class FlickrSearchApplication extends Application implements OnProgressListener {
	/** Number of images to request from Flickr */
	public static final int MAX_IMAGES = 20;
	// In mem caches
	public boolean isSearching;
	/** Current set of images (not this does not include image bitmap data) */
	private List<FlickrImage> images = new ArrayList<FlickrImage>();
	/** Ids of images that have not been downloaded yet */
	private List<Long> pendingDownloads = new ArrayList<Long>();
	/** Notification Manager */
	private NotificationManager nMan;
	// Service relate
	/** Currently visible activity (will receive callbacks) */
	private AbstractBaseActivity currentActivity;
	/** Service message handler */
	private Handler serviceHandler; 
	/** Queue of message that need to be sent to local service. */
	private List<Message> queue = new ArrayList<Message>();
	/** HttpClient used for network downloading */
	private DefaultHttpClient httpClient;
	/** Executor used to perform service functionality async. */
	private Executor serviceExecutor = Executors.newFixedThreadPool(5, new ThreadFactory() {
		public Thread newThread(Runnable r) {
			Thread t = new Thread(r);
			t.setPriority(Thread.MIN_PRIORITY);
			return t;
		}
	});

	@Override
	public void onCreate() {
		super.onCreate();
		nMan = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		
		// Setup HTTPClient
		HttpParams params = new BasicHttpParams();
		HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
		HttpProtocolParams.setContentCharset(params, "utf-8");
		params.setBooleanParameter("http.protocol.expect-continue", false);
		SchemeRegistry registry = new SchemeRegistry();
		registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
		ThreadSafeClientConnManager manager = new ThreadSafeClientConnManager(params, registry);
		httpClient = new DefaultHttpClient(manager,params);

		
	}
	
	// ------------------
	// GETTERS
	// ------------------
	
	public DefaultHttpClient getHttpClient() { return httpClient; }
	public Executor getServiceExecutor() { return serviceExecutor; }
	
	// ------------------
	// MAIN FUNCTIONALITY 
	// ------------------
	
	/**
	 * Returns current search images
	 * @return List of images
	 */
	public List<FlickrImage> getImages() { return images; }
	/**
	 * Returns an image give its id.
	 * returns null if image does not exist.
	 * @param imageId The image id.
	 * @return The image or null.
	 */
	public FlickrImage getImage(long imageId) {
		for (FlickrImage image : images) {
			if (image.id == imageId)
				return image;
		}
		return null;
	}
	/**
	 * Returns the number of images that have been downloaded so far.
	 * @return The number of downloaded images.
	 */
	public int getProgress() { return images.size() - pendingDownloads.size(); }
	
	/**
	 * Does a Flickr search
	 * @param searchTerm The search term to use.
	 */
	public void search(String searchTerm) {
		if (isSearching)
			return;
		isSearching = true;
		images.clear();
		if (currentActivity != null)
			currentActivity.searchStarted(searchTerm);
		updateTaskBar();
		postToService(FlickrSearchService.ACTION_SEARCH, searchTerm);
	}
	
	/**
	 * Returns the bitmap for an image, or null if it has not been downloaded yet.
	 * If the image has not been downloaded, null is returned and it scheduled for download.
	 * @param image The image
	 * @param isThumbnail Should download the image thumbnail
	 * @return The bitmap or null
	 */
	public Bitmap getImageBitmap(FlickrImage image, boolean isThumbnail) {
		DownloadInfo request = new DownloadInfo(image,isThumbnail);
		Bitmap bitmap = CacheUtil.getBitmap(request.getCacheKey());
		if (bitmap != null)
			return bitmap;
		// Nope so schedule download
		postToService(FlickrSearchService.ACTION_DOWNLOAD, request);
		return null;
	}
		
	public Bitmap getImageBitmap(String cacheKey) {
		return CacheUtil.getBitmap(cacheKey);
	}
		
	// ------------------
	// CALLBACKS
	// ------------------
	
	public void searchError(Exception e) {
		isSearching = false;
		images.clear();
		pendingDownloads.clear();
		if (currentActivity != null) {
			currentActivity.searchError(e);
		}
	}
	
	/**
	 * Callback from service once a search is done.
	 * @param images The images.
	 */
	public void setImages(List<FlickrImage> images) {
		this.images = images;
		pendingDownloads.clear();
		if (images.size() == 0) 
			isSearching = false;

		for (FlickrImage image : images) {
			pendingDownloads.add(image.id);
			// Schedule thumbnail download
			getImageBitmap(image, true);
		}
		for (FlickrImage image : images) {
			// Schedule image download
			getImageBitmap(image, false);
		}
		if (currentActivity != null) {
			currentActivity.searchResult(images);
			if (images.size() == 0)
				currentActivity.searchDone();
		}
		updateTaskBar();
	}
	
	/**
	 * Callback from service when image has been downloaded.
	 * @param info The image download info.
	 */
	public void bitmapDownloaded(DownloadInfo info) {
		if (!info.isThumbnail) {
			pendingDownloads.remove((Long)info.image.id);
			if (pendingDownloads.size() == 0)
				isSearching = false;
		}
		if (currentActivity != null) {
			currentActivity.bitmapDownloaded(info);
			if (pendingDownloads.size() == 0) {
				nMan.cancelAll();
				currentActivity.searchDone();
			}
		} 
		updateTaskBar();
	}
	
	/**
	 * Callback that provides progress information.
	 */
	@Override
	public void onProgress(int percentage, Object tag) {
		FlickrImage image = (FlickrImage) tag;
		if (currentActivity != null) {
			currentActivity.onProgress(image,percentage);
		}
	}
	
	// ------------------
	// NOTIFICATIONS
	// ------------------
	
	/**
	 * Clear taskbar notifications
	 */
	public void clearNotifications() {
		nMan.cancelAll();
	}
	
	/**
	 * Update task bar notification
	 */
	private void updateTaskBar() {
		RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.notification);
		
		int size = images.size();
		int progress = getProgress();
		
		contentView.setProgressBar(R.id.notification_progress, size, progress, false);
		if (images.size() > 0)
			contentView.setTextViewText(R.id.notification_text, "Downloaded "+progress+"/"+size+" images");
		else
			contentView.setTextViewText(R.id.notification_text, "Downloading Images");

		String text = "Downloading images";
		if (!isSearching) {
			contentView.setTextViewText(R.id.notification_text, "Download complete");
			text = "Download complete";
		}
		
		Intent notificationIntent = new Intent(this,MainActivity.class);
		PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

		Notification notification = new Notification(R.drawable.icon, text, System.currentTimeMillis());
		notification.contentView = contentView;
		notification.contentIntent = contentIntent;
		nMan.notify(1,notification);
	}
	
	// ------------------
	// SERVICE RELATED
	// ------------------
	
	/**
	 * Sets the current activity.
	 * This should be called by the activities onResume and onPause methods
	 * @param activity The activity or null.
	 */
	public void setCurrentActivity(AbstractBaseActivity activity) {
		this.currentActivity = activity;
	}
	
	/**
	 * Service calls this to register it's message handler.
	 * @param handler The message handler.
	 */
	public void registerServiceHandler(Handler handler) {
		serviceHandler = handler;
		if (serviceHandler != null) {
			// May have pending messages, send them now.
			synchronized (queue) {
				for (Message msg : queue) {
					serviceHandler.sendMessage(msg);
				}
				queue.clear();
			}
		}
	}
	
	/**
	 * Post a new service request
	 * @param action Action to be performed by service
	 * @param objects Any additional information required by the service.
	 */
	private void postToService(int action, Object... params) {
		Message message = Message.obtain();
		message.what = action;
		message.obj = params;
		if (serviceHandler != null) {
			synchronized (serviceHandler) {
				serviceHandler.sendMessage(message);
			}
		} else {
			synchronized (queue) {
				queue.add(message);
			}
			startService(new Intent(this,FlickrSearchService.class));
		}
	}
}
